﻿using HMS.Common;
using HMS.Contracts.Models;
using HMS.Core;
using HMS.Core.Abstractions;
using HMS.Core.DataProtocol;
using HMS.Core.Exceptions;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using System.Net.Http.Json;
using System.Security.Claims;

namespace HMS.Services.Impl;

internal class TenantService : ITenantService
{
    private string? _tenantId;

    private readonly HttpClient _httpClient;
    private readonly IMemoryCache _memoryCache;
    private readonly IOptionsMonitor<TenantServerOptions> _optionsMonitor;

    private string TenantCacheKey => $"tenant-{_tenantId}";

    public TenantService(IHttpClientFactory factory, IMemoryCache memoryCache, IOptionsMonitor<TenantServerOptions> optionsMonitor)
    {
        _httpClient = factory.CreateClient("oauth");
        _memoryCache = memoryCache;
        _optionsMonitor = optionsMonitor;
    }

    public TenantModel Tenant
    {
        get
        {
            var tenant = _memoryCache.Get<TenantModel>(TenantCacheKey);

            return tenant is not null ? tenant : throw new TenantNotAuthenticatedException("租户未登录");
        }
    }

    public async Task<OperationalResult<Token>> AuthenticateAsync(TenantLoginModel model, CancellationToken cancellationToken = default)
    {
        var content = BuildPasswordMode(model.Account, model.Password, model.IsRoot);

        var tenantServerOpts = _optionsMonitor.CurrentValue;

        var tenantServerHost = tenantServerOpts.Port.HasValue ? $"{tenantServerOpts.Host}:{tenantServerOpts.Port}" : tenantServerOpts.Host;

        var response = await _httpClient.PostAsync($"https://{tenantServerHost}/connect/token", content, cancellationToken);

        var result = new OperationalResult<Token>();

        try
        {
            var loginResponse = await response.Content.ReadFromJsonAsync<Token>(cancellationToken: cancellationToken);

            if (loginResponse is not null)
            {
                SetTenantId(loginResponse.Tenant.TenantId);

                _memoryCache.Set(TenantCacheKey, loginResponse.Tenant);

                result.Data = loginResponse;

                return result;
            }

            throw new TenantNotAuthenticatedException("租户登录失败");
        }
        catch (Exception)
        {
            result.Status = OperationalResult.FAILURE;

            result.Message = "租户登录失败，如果是IAM用户登录，请取消勾选Root用户";

            return result;
        }
    }

    public void SetTenantId(string tenantId)
    {
        if (!string.IsNullOrEmpty(tenantId))
        {
            _tenantId = tenantId;
        }
    }

    public string GetSchema()
    {
        var tenant = _memoryCache.Get<TenantModel>(TenantCacheKey);

        return tenant is not null ? tenant.Schema : string.Empty;
    }

    public Task Validate(TokenValidatedContext context)
    {
        var tenantId = context.Principal.FindFirstValue("tenant");

        if (!string.IsNullOrEmpty(tenantId))
        {
            SetTenantId(tenantId);
        }

        return Task.CompletedTask;
    }

    private static FormUrlEncodedContent BuildPasswordMode(string username, string password, bool isRoot = true, string? scope = null)
    {
        var data = PasswordData(username, password, isRoot, scope);

        return new FormUrlEncodedContent(data);
    }

    private static IEnumerable<KeyValuePair<string, string>> PasswordData(string username, string password, bool isRoot = true, string? scope = null)
    {
        return new Dictionary<string, string>
        {
            { "grant_type", "password" },
            { "isroot", isRoot.ToString() },
            { "username", username },
            { "password", password },
            { "scope", scope ?? string.Empty }
        };
    }
}